package com.gitee.l0km.javadocreader;

import java.util.List;

import com.sun.source.doctree.*;
import com.sun.source.doctree.AttributeTree.ValueKind;
import com.sun.source.util.SimpleDocTreeVisitor;

/**
 * 实现注释主体内容或标签内容输出到{@link StringBuilder}<br>
 * 注释主体内容与包含的标签分开输出，即
 * 当输出注释主体内容时不输出包含的标签内容({@link BlockTagTree}
 */
class CommentTextRender extends SimpleDocTreeVisitor<String,StringBuilder> {
	private final String lineSep = System.getProperty("line.separator");
	/** 输出注释内容时是否输出块标签(block tag) */
	private boolean withBlockTags;
	/** 输出块标签(block tag)时是否输出标签名,如'@author' */
	private boolean withTag;
	/** 输出参数块标签('@param')时是否输出参数名 */
	private boolean withParamName;
	/** 输出块标签(block tag)时结尾是否输出换行符 */
	private boolean withTagNewLine;

	private CommentTextRender(boolean withBlockTags, boolean withTag, boolean withParamName, boolean withTagNewLine) {
		this.withBlockTags = withBlockTags;
		this.withTag = withTag;
		this.withParamName = withParamName;
		this.withTagNewLine = withTagNewLine;
	}

	CommentTextRender() {
		this(false,false,false, false);
    }
	CommentTextRender withBlockTags() {
		return new CommentTextRender(true,this.withTag,this.withParamName, this.withTagNewLine);
	}
	CommentTextRender withTag() {
		return new CommentTextRender(this.withBlockTags, true,this.withParamName, this.withTagNewLine);
	}
	CommentTextRender withParamName() {
		return new CommentTextRender(this.withBlockTags,this.withTag, true, this.withTagNewLine);
	}
	CommentTextRender withTagNewLine() {
		return new CommentTextRender(this.withBlockTags,this.withTag, this.withParamName, true);
	}
	CommentTextRender withFullTag() {
		return new CommentTextRender(this.withBlockTags,true, true, true);
	}
	String render(DocTree docTree) {
		StringBuilder builder=new StringBuilder();
		if(null != docTree) {
			return  docTree.accept(this,builder);
		}
		return builder.toString();
	}
	/** Visitor method: print expression tree. */
    protected void print(DocTree tree, StringBuilder builder) {
    	if (tree == null)
    		print("/*missing*/", builder);
    	else {
    		tree.accept(this, builder);
    	}
    }

    /** Print string. */
    protected void print(Object s, StringBuilder builder) {
    	builder.append(s);
    }
    /** Print string. */
    protected void printIf(boolean cond, Object s, StringBuilder builder) {
    	if(cond) {
    		builder.append(s);
    	}
    }

    /** Print list with separators.   */
    protected StringBuilder print(List<? extends DocTree> list, String sep, StringBuilder builder) {
        if (list.isEmpty())
            return builder;
        boolean first = true;
        for (DocTree t: list) {
            if (!first)
                print(sep, builder);
            visit(t, builder);
            first = false;
        }
        return builder;
    }

	/** Print list with separators. */
	protected StringBuilder printWithNewLine(List<? extends DocTree> list, StringBuilder builder) {
		return print(list, withTagNewLine ? lineSep : "", builder);
	}

    protected StringBuilder printTagName(DocTree node, StringBuilder builder) {
    	builder.append("@");
    	builder.append(node.getKind().tagName);
    	return builder;
    }
    /** Print new line.  */
    protected void println(StringBuilder builder) {
    	builder.append(lineSep);
    }
    /* ************************************************************************
     * Traversal methods
     *************************************************************************/
//	protected String visitBlockTag(BlockTagTree node, StringBuilder p) {
//		return defaultAction(node, p);
//    }
    @Override
	protected String defaultAction(DocTree node, StringBuilder p) {
    	if(node instanceof BlockTagTree || node instanceof DocCommentTree) {
    		return p.toString();
    	}
		return super.defaultAction(node, p);
	}
    @Override
    public String visitAttribute(AttributeTree node, StringBuilder p) {
        print(node.getName(), p);
		String quote;
		switch (node.getValueKind()) {
		    case EMPTY:
		        quote = null;
		        break;
		    case UNQUOTED:
		        quote = "";
		        break;
		    case SINGLE:
		        quote = "'";
		        break;
		    case DOUBLE:
		        quote = "\"";
		        break;
		    default:
		        throw new AssertionError();
		}
		if (quote != null) {
		    print("=" + quote, p);
		    visit(node.getValue(), p);
		    print(quote, p);
		}
		 return defaultAction(node,p);
    }

    @Override 
    public String visitAuthor(AuthorTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
		visit(node.getName(), p);
		 return defaultAction(node,p);
    }

    @Override 
    public String visitComment(CommentTree node, StringBuilder p) {
        print(node.getBody(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitDeprecated(DeprecatedTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
		visit(node.getBody(), p);
        return defaultAction(node,p);
    }

	@Override
	public String visitDocComment(DocCommentTree node, StringBuilder p) {
		visit(node.getFullBody(), p);
		if (withBlockTags) {
			println(p);
			boolean _wtithTag = withTag;
			boolean _withParamName = withParamName;
			boolean _withTagNewLine = withTagNewLine;
			try {
				withTag = true;
				withParamName = true;
				withTagNewLine = true;
				printWithNewLine(node.getBlockTags(), p);
			} finally {
				withTag = _wtithTag;
				withParamName = _withParamName;
				withTagNewLine = _withTagNewLine;
			}
		}
		return defaultAction(node, p);
	}

    @Override 
    public String visitDocRoot(DocRootTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitDocType(DocTypeTree node, StringBuilder p) {
        print(node.getText(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitEndElement(EndElementTree node, StringBuilder p) {
        print("</", p);
		print(node.getName(), p);
		print(">", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitEntity(EntityTree node, StringBuilder p) {
        print("&", p);
		print(node.getName(), p);
		print(";", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitErroneous(ErroneousTree node, StringBuilder p) {
        print(node.getBody(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitHidden(HiddenTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
		visit(node.getBody(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitIdentifier(IdentifierTree node, StringBuilder p) {
        print(node.getName(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitIndex(IndexTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		print(" ", p);
		visit(node.getSearchTerm(), p);
		if (!node.getDescription().isEmpty()) {
		    print(" ", p);
		    visit(node.getDescription(), p);
		}
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitInheritDoc(InheritDocTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitLink(LinkTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		print(" ", p);
		visit(node.getReference(), p);
		if (!node.getLabel().isEmpty()) {
		    print(" ", p);
		    visit(node.getLabel(), p);
		}
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitLiteral(LiteralTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		String body = node.getBody().getBody();
		if (!body.isEmpty() && !Character.isWhitespace(body.charAt(0))) {
		    print(" ", p);
		}
		visit(node.getBody(), p);
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitParam(ParamTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    	}
		if (withParamName) {
			printIf(withTag, " ", p);
			if (node.isTypeParameter())	print("<", p);
			visit(node.getName(), p);
			if (node.isTypeParameter()) print(">", p);
		}
		printIf((withTag || withParamName) && !node.getDescription().isEmpty()," ", p);
    	/** 只输出参数描述 */
		visit(node.getDescription(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitProvides(ProvidesTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getServiceType(),p);
    	print(" ", p);
    	visit(node.getDescription(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitReference(ReferenceTree node, StringBuilder p) {
        print(node.getSignature(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitReturn(ReturnTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
		visit(node.getDescription(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSee(SeeTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getReference(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSerial(SerialTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getDescription(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSerialData(SerialDataTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getDescription(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSerialField(SerialFieldTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getName(),p);
    	print(" ",p);
    	visit(node.getType(),p);    	
		if (!node.getDescription().isEmpty()) {
            print(" ",p);
            visit(node.getDescription(),p);
        }
        return defaultAction(node,p);
    }

    @Override 
    public String visitSince(SinceTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getBody(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSnippet(SnippetTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		List<? extends DocTree> attrs = node.getAttributes();
		if (!attrs.isEmpty()) {
		    print(" ", p);
		    print(attrs, " ", p);
		}
		if (node.getBody() != null) {
		    print(" :\n", p);
		    print(node.getBody(), p);
		}
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitStartElement(StartElementTree node, StringBuilder p) {
        print("<", p);
		print(node.getName(), p);
		List<? extends DocTree> attrs = node.getAttributes();
		if (!attrs.isEmpty()) {
		    print(" ", p);
		    print(attrs, " ", p);
		    DocTree last = node.getAttributes().get(attrs.size() - 1);
		    if (node.isSelfClosing() && last instanceof AttributeTree
		            && ((AttributeTree)last).getValueKind() == ValueKind.UNQUOTED)
		        print(" ", p);
		}
		if (node.isSelfClosing())
		    print("/", p);
		print(">", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSummary(SummaryTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		if (!node.getSummary().isEmpty()) {
		    print(" ", p);
		    printWithNewLine(node.getSummary(), p);
		}
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitSystemProperty(SystemPropertyTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		print(" ", p);
		print(node.getPropertyName(), p);
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitText(TextTree node, StringBuilder p) {
        print(node.getBody(), p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitThrows(ThrowsTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getExceptionName(),p);
    	if(!node.getDescription().isEmpty()) {
    		print(" ",p);
    		visit(node.getDescription(),p);
    	}
        return defaultAction(node,p);
    }

    @Override 
    public String visitUnknownBlockTag(UnknownBlockTagTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getContent(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitUnknownInlineTag(UnknownInlineTagTree node, StringBuilder p) {
        print("{", p);
		print("@", p);
		print(node.getTagName(), p);
		print(" ", p);
		visit(node.getContent(), p);
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitUses(UsesTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getServiceType(),p);
    	if (!node.getDescription().isEmpty()) {
            print(" ",p);
            visit(node.getDescription(),p);
        }
        return defaultAction(node,p);
    }

    @Override 
    public String visitValue(ValueTree node, StringBuilder p) {
        print("{", p);
		printTagName(node, p);
		if (node.getReference() != null) {
		    print(" ", p);
		    visit(node.getReference(), p);
		}
		print("}", p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitVersion(VersionTree node, StringBuilder p) {
    	if(withTag) {
    		printTagName(node,p);
    		print(" ", p);
    	}
    	visit(node.getBody(),p);
        return defaultAction(node,p);
    }

    @Override 
    public String visitOther(DocTree node, StringBuilder p) {
        print("(UNKNOWN: " + node + ")", p);
        return defaultAction(node,p);
    }
}